package com.nbcio.cd.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nbcio.cd.config.CdInitializer;
import com.nbcio.cd.domain.CdFile;
import com.nbcio.cd.domain.CdFolder;
import com.nbcio.cd.domain.CdUserStorage;
import com.nbcio.cd.domain.vo.CdFileVo;
import com.nbcio.cd.enums.FileTypeEnum;
import com.nbcio.cd.mapper.CdFileMapper;
import com.nbcio.cd.mapper.CdFolderMapper;
import com.nbcio.cd.mapper.CdUserStorageMapper;
import com.nbcio.cd.service.ICdFileService;
import com.nbcio.cd.utils.CdFileUtils;
import com.nbcio.cd.utils.CdPathUtils;
import com.nbcio.cd.utils.CdPermissionUtils;
import com.nbcio.common.core.domain.R;
import com.nbcio.common.core.utils.MapstructUtils;
import com.nbcio.common.core.utils.StringUtils;
import com.nbcio.common.oss.entity.UploadResult;
import com.nbcio.common.satoken.utils.LoginHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 云网盘文件Service业务层处理
 *
 * @author nbacheng
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class CdFileServiceImpl extends ServiceImpl<CdFileMapper, CdFile> implements ICdFileService {

    /**
     * 文件上传大小限制，从配置文件读取，默认200MB
     */
    @Value("${spring.servlet.multipart.max-file-size:200MB}")
    private String maxFileSize;

    private final CdPermissionUtils permissionUtils;

    private final CdFileMapper cdFileMapper;
    private final CdFolderMapper cdFolderMapper;
    private final CdUserStorageMapper cdUserStorageMapper;
    private final CdInitializer cdInitializer;

    /**
     * 上传文件
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CdFileVo uploadFile(MultipartFile file, Long folderId) {
        try {
            log.info("开始上传文件，文件名: {}, 文件夹ID: {}", file.getOriginalFilename(), folderId);
            
            // 验证文件
            validateFile(file);
            
            // 验证文件夹
            CdFolder folder = validateFolder(folderId);
            if (folder != null) {
                log.info("验证文件夹成功: {}", folder.getFolderName());
            } else {
                log.info("验证文件夹成功: 根文件夹（我的文件）");
            }
            
            // 检查用户存储空间
            checkUserStorageSpace(file.getSize());
            
            // 生成文件信息
            String originalName = file.getOriginalFilename();
            String fileExtension = CdFileUtils.getFileExtension(originalName);
            String fileName = CdFileUtils.generateUniqueFileName(originalName);
            String fileHash = CdFileUtils.calculateFileHash(file.getInputStream());
            
            // 检查是否已存在相同文件
            CdFile existingFile = cdFileMapper.selectFileByHash(fileHash);
            if (existingFile != null) {
                // 如果存在相同文件，创建引用而不是重新上传
                return createFileReference(existingFile, folderId, originalName);
            }
            
            // 上传文件到存储
            String filePath = uploadFileToStorage(file, fileName);
            
            // 创建文件记录
            CdFile cdFile = new CdFile();
            cdFile.setFileName(fileName);
            cdFile.setOriginalName(originalName);
            cdFile.setFileExtension(fileExtension);
            cdFile.setFileSize(file.getSize());
            cdFile.setFileType(CdFileUtils.getFileType(originalName).getCode());
            cdFile.setMimeType(file.getContentType());
            cdFile.setFileHash(fileHash);
            cdFile.setFolderId(folderId);
            cdFile.setFilePath(filePath);
            cdFile.setDownloadCount(0);
            cdFile.setViewCount(0);
            cdFile.setIsShared(false);
            cdFile.setAccessLevel("private");
            cdFile.setVersion(1);
            cdFile.setStatus("0");
            
            // 保存文件记录
            cdFileMapper.insert(cdFile);
            log.info("文件记录保存成功，文件ID: {}", cdFile.getFileId());
            
            // 更新文件夹统计
            updateFolderStats(folderId);
            
            // 更新用户存储统计
            updateUserStorageStats(file.getSize(), 1, 0);
            
            // 转换为VO返回
            CdFileVo fileVo = buildFileVo(cdFile);
            log.info("文件上传完成，返回文件VO: {}", fileVo.getFileName());
            return fileVo;
            
        } catch (Exception e) {
            log.error("上传文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("上传文件失败: " + e.getMessage());
        }
    }

    /**
     * 下载文件
     */
    @Override
    public void downloadFile(Long fileId, HttpServletResponse response) {
        try {
            log.info("开始下载文件，文件ID: {}", fileId);
            // 获取文件信息
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                log.error("文件不存在，文件ID: {}", fileId);
                throw new RuntimeException("文件不存在");
            }
            
            log.info("文件信息获取成功: {}", cdFile.getOriginalName());
            
            // 注：该接口已在 application.yml 中配置了排除路径，不进行 Sa-Token 认证
            // 因此在这里无法获取当前用户ID
            // 为了让预览功能正常工作，暂时允许任何人下载文件（需要通过其他方式控制访问）
            log.warn("文件下载/预览接口: 用户ID无法获取（未认证），允许任何人访问文件ID={}", fileId);
            // 检查权限：先检查是否为文件所有者
            /*Long currentUserId = LoginHelper.getUserId();
            
            // 如果文件不是当前用户创建的，检查权限
            if (cdFile.getCreateBy() == null || !currentUserId.equals(cdFile.getCreateBy())) {
                permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_READ, "无权限下载此文件");
            }*/
            // 更新下载次数
            cdFileMapper.updateDownloadCount(fileId);
            
            // 设置响应头
            String fileName = URLEncoder.encode(cdFile.getOriginalName(), StandardCharsets.UTF_8);
            // 根据文件类型设置Content-Type
            String contentType = cdFile.getMimeType();
            if (contentType == null || contentType.isEmpty()) {
                // 根据文件扩展名推断
                String extension = CdFileUtils.getFileExtension(cdFile.getOriginalName());
                contentType = switch (extension.toLowerCase()) {
                    case "jpg", "jpeg" -> "image/jpeg";
                    case "png" -> "image/png";
                    case "gif" -> "image/gif";
                    case "pdf" -> "application/pdf";
                    default -> "application/octet-stream";
                };
            }
            response.setContentType(contentType);
            // 预览接口和下载接口共享，通过inline方式让浏览器决定是显示还是下载
            response.setHeader("Content-Disposition", "inline; filename=\"" + fileName + "\"");
            response.setHeader("Content-Length", String.valueOf(cdFile.getFileSize()));
            
            // 获取文件内容并写入响应
            String uploadType = com.nbcio.common.core.config.RuoYiConfig.getUploadtype();
            if ("local".equals(uploadType)) {
                // 本地文件
                String profile = com.nbcio.common.core.config.RuoYiConfig.getProfile();
                // FileUploadUtils.upload 返回的路径格式是 /profile/upload/cd/...
                // 需要去掉 /profile 前缀，然后拼接真实的 profile 路径
                String relativePath = cdFile.getFilePath();
                if (relativePath.startsWith("/profile")) {
                    relativePath = relativePath.substring(8); // 去掉 "/profile" 前缀
                }
                String fullPath = profile + relativePath;
                File file = new File(fullPath);
                
                log.info("原始filePath: {}", cdFile.getFilePath());
                log.info("处理后的相对路径: {}", relativePath);
                log.info("完整路径: {}", fullPath);
                
                if (!file.exists()) {
                    log.error("物理文件不存在，路径: {}", fullPath);
                    throw new RuntimeException("文件不存在");
                }
                
                try (FileInputStream fis = new FileInputStream(file);
                     OutputStream os = response.getOutputStream()) {
                    
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = fis.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                    os.flush();
                }
            } else {
                // OSS文件
                com.nbcio.common.oss.core.OssClient ossClient = com.nbcio.common.oss.factory.OssFactory.instance();
                try (java.io.InputStream inputStream = ossClient.getObjectContent(cdFile.getFilePath());
                     OutputStream os = response.getOutputStream()) {
                    
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                    os.flush();
                }
            }
            
        } catch (Exception e) {
            log.error("下载文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("下载文件失败: " + e.getMessage());
        }
    }

    /**
     * 预览文件
     */
    @Override
    public String previewFile(Long fileId) {
        try {
            // 获取文件信息
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                throw new RuntimeException("文件不存在");
            }
            
            // 检查权限
            permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_READ, "无权限预览此文件");
            
            // 检查是否支持预览
            if (!CdFileUtils.canPreview(cdFile.getOriginalName())) {
                throw new RuntimeException("此文件类型不支持预览");
            }
            
            // 返回预览URL - 使用download接口
            // 这样可以复用现有的下载逻辑，不需要创建新的接口
            return "/cd/file/download/" + fileId;
            
        } catch (Exception e) {
            log.error("预览文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("预览文件失败: " + e.getMessage());
        }
    }

    /**
     * 预览Office文档 - 返回HTML内容
     * 注意：该接口已在 security.excludes 中，不进行 token 认证
     * 这里不进行权限检查，允许任何人预览（TODO: 后续可以添加其他安全措施）
     */
    @Override
    public void previewHtml(Long fileId, HttpServletResponse response) {
        try {
            // 获取文件信息
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                throw new RuntimeException("文件不存在");
            }
            
            // 不进行权限检查（已在 security.excludes 中，允许公开访问）
            // TODO: 可以添加其他安全措施，如 IP 白名单、Referer 检查等
            
            // 获取文件内容（复用 downloadFile 的逻辑获取文件流）
            java.io.File file = getPhysicalFile(cdFile);
            if (file == null || !file.exists()) {
                throw new RuntimeException("文件不存在");
            }
            
            // 根据文件类型生成HTML
            String html = generatePreviewHtml(cdFile, file);
            
            // 设置响应头
            response.setContentType("text/html;charset=UTF-8");
            response.setHeader("Cache-Control", "no-cache");
            
            // 输出HTML
            response.getWriter().write(html);
            
        } catch (Exception e) {
            log.error("预览HTML失败: {}", e.getMessage(), e);
            try {
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.setCharacterEncoding("UTF-8");
                String errorHtml = "<html><head><meta charset='UTF-8'></head><body><h1>预览失败</h1><p>" + e.getMessage() + "</p></body></html>";
                response.getWriter().write(errorHtml);
            } catch (Exception ignored) {
            }
        }
    }
    
    /**
     * 获取物理文件
     */
    private java.io.File getPhysicalFile(CdFile cdFile) {
        String uploadType = com.nbcio.common.core.config.RuoYiConfig.getUploadtype();
        if ("local".equals(uploadType)) {
            String profile = com.nbcio.common.core.config.RuoYiConfig.getProfile();
            String relativePath = cdFile.getFilePath();
            if (relativePath.startsWith("/profile")) {
                relativePath = relativePath.substring(8);
            }
            String fullPath = profile + relativePath;
            return new java.io.File(fullPath);
        }
        return null;
    }
    
    /**
     * 生成预览HTML
     */
    private String generatePreviewHtml(CdFile cdFile, java.io.File file) {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html><html><head><meta charset='UTF-8'>");
        html.append("<title>").append(cdFile.getOriginalName()).append("</title>");
        html.append("<style>");
        html.append("body { font-family: Arial, sans-serif; padding: 20px; line-height: 1.6; }");
        html.append(".header { border-bottom: 2px solid #409EFF; padding-bottom: 10px; margin-bottom: 20px; }");
        html.append(".content { white-space: pre-wrap; word-wrap: break-word; }");
        html.append(".warning { background: #fff3cd; border: 1px solid #ffc107; padding: 15px; border-radius: 4px; margin: 20px 0; }");
        html.append("</style></head><body>");
        html.append("<div class='header'><h1>").append(cdFile.getOriginalName()).append("</h1></div>");
        html.append("<div class='content'>");
        
        String fileName = cdFile.getOriginalName().toLowerCase();
        
        try {
            if (fileName.endsWith(".txt") || fileName.endsWith(".log") || fileName.endsWith(".md")) {
                // 文本文件
                String content = new String(java.nio.file.Files.readAllBytes(file.toPath()), "UTF-8");
                html.append(content);
            } else if (fileName.endsWith(".docx")) {
                // Word 2007+ 文档
                html.append(readDocxContent(file));
            } else if (fileName.endsWith(".doc")) {
                // Word 2003 文档（需要 hwpf 库支持）
                html.append(readDocContent(file));
            } else if (fileName.endsWith(".xlsx") || fileName.endsWith(".xls")) {
                // Excel 文档（支持 2003 和 2007+）
                html.append(readExcelContent(file));
            } else if (fileName.endsWith(".pptx")) {
                // PowerPoint 2007+
                html.append("<div class='warning'>PowerPoint 文件暂不支持在线预览，请下载后使用 PowerPoint 打开。</div>");
            } else if (fileName.endsWith(".ppt")) {
                // PowerPoint 2003
                html.append("<div class='warning'>此文件格式暂不支持在线预览，请下载后使用 PowerPoint 打开。</div>");
            } else {
                html.append("<div class='warning'>此文件类型暂不支持在线预览，请下载后使用相应软件打开。</div>");
            }
        } catch (Exception e) {
            log.error("生成预览HTML失败: {}", e.getMessage(), e);
            html.append("<div class='warning'>无法读取文件内容: ").append(e.getMessage()).append("</div>");
        }
        
        html.append("</div></body></html>");
        return html.toString();
    }
    
    /**
     * 读取 Word 2007+ 文档内容
     */
    private String readDocxContent(java.io.File file) {
        try {
            XWPFDocument document = new XWPFDocument(new java.io.FileInputStream(file));
            
            StringBuilder content = new StringBuilder();
            
            // 读取所有段落
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                String text = paragraph.getText();
                if (text != null && !text.trim().isEmpty()) {
                    content.append(text).append("\n");
                }
            }
            
            document.close();
            return content.toString();
        } catch (Exception e) {
            log.error("读取Word文档失败: {}", e.getMessage(), e);
            return "<div class='warning'>无法读取Word文档内容: " + e.getMessage() + "</div>";
        }
    }
    
    /**
     * 读取 Word 2003 文档内容（.doc）
     * 注意：需要 poi-scratchpad 依赖，暂时不支持
     */
    private String readDocContent(java.io.File file) {
        return "<div class='warning'>Word 2003 格式(.doc)暂不支持在线预览，请下载后使用 Word 打开或转换为 .docx 格式。</div>";
    }
    
    /**
     * 读取 Excel 文档内容（支持 .xls 和 .xlsx）
     */
    private String readExcelContent(java.io.File file) {
        try {
            FileInputStream fis = new FileInputStream(file);
            Workbook workbook;
            
            // 根据文件扩展名选择不同的 Workbook 实现
            if (file.getName().toLowerCase().endsWith(".xls")) {
                workbook = new HSSFWorkbook(fis); // Excel 2003
            } else {
                workbook = new XSSFWorkbook(fis); // Excel 2007+
            }
            
            StringBuilder content = new StringBuilder();
            
            // 读取第一个工作表的内容（限制行数，避免太大）
            Sheet sheet = workbook.getSheetAt(0);
            int maxRows = Math.min(sheet.getLastRowNum() + 1, 100); // 最多显示100行
            
            for (int i = 0; i < maxRows; i++) {
                Row row = sheet.getRow(i);
                if (row != null) {
                    for (int j = 0; j < row.getLastCellNum(); j++) {
                        Cell cell = row.getCell(j);
                        if (cell != null) {
                            content.append(getCellValue(cell)).append("\t");
                        }
                    }
                    content.append("\n");
                }
            }
            
            workbook.close();
            fis.close();
            return content.toString();
        } catch (Exception e) {
            log.error("读取Excel文档失败: {}", e.getMessage(), e);
            return "<div class='warning'>无法读取Excel文档内容: " + e.getMessage() + "</div>";
        }
    }
    
    /**
     * 获取单元格值
     */
    private String getCellValue(Cell cell) {
        try {
            switch (cell.getCellType()) {
                case STRING:
                    return cell.getStringCellValue();
                case NUMERIC:
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return cell.getDateCellValue().toString();
                    } else {
                        return String.valueOf(cell.getNumericCellValue());
                    }
                case BOOLEAN:
                    return String.valueOf(cell.getBooleanCellValue());
                case FORMULA:
                    return cell.getCellFormula();
                default:
                    return "";
            }
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 重命名文件
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean renameFile(Long fileId, String newName) {
        try {
            // 获取文件信息
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                throw new RuntimeException("文件不存在");
            }
            
            // 检查权限
            permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_WRITE, "无权限重命名此文件");
            
            // 验证新名称
            if (StrUtil.isBlank(newName)) {
                throw new RuntimeException("文件名不能为空");
            }
            
            // 检查同级目录下是否有重名文件
            String fileExtension = CdFileUtils.getFileExtension(cdFile.getOriginalName());
            String newFileName = newName + (StrUtil.isNotBlank(fileExtension) ? "." + fileExtension : "");
            
            LambdaQueryWrapper<CdFile> wrapper = new LambdaQueryWrapper<CdFile>();
            wrapper.eq(CdFile::getFolderId, cdFile.getFolderId())
                   .eq(CdFile::getOriginalName, newFileName)
                   .ne(CdFile::getFileId, fileId);
            
            if (cdFileMapper.selectCount(wrapper) > 0) {
                throw new RuntimeException("同级目录下已存在同名文件");
            }
            
            // 更新文件信息
            cdFile.setOriginalName(newFileName);
            cdFileMapper.updateById(cdFile);
            
            return true;
            
        } catch (Exception e) {
            log.error("重命名文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("重命名文件失败: " + e.getMessage());
        }
    }

    /**
     * 移动文件
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean moveFile(Long fileId, Long targetFolderId) {
        try {
            // 获取文件信息
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                throw new RuntimeException("文件不存在");
            }
            
            // 检查权限
            permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_WRITE, "无权限移动此文件");
            
            // 验证目标文件夹
            CdFolder targetFolder = validateFolder(targetFolderId);
            
            // 检查目标文件夹下是否有重名文件
            LambdaQueryWrapper<CdFile> wrapper = new LambdaQueryWrapper<CdFile>();
            wrapper.eq(CdFile::getFolderId, targetFolderId)
                   .eq(CdFile::getOriginalName, cdFile.getOriginalName())
                   .ne(CdFile::getFileId, fileId);
            
            if (cdFileMapper.selectCount(wrapper) > 0) {
                throw new RuntimeException("目标文件夹下已存在同名文件");
            }
            
            // 更新文件信息
            cdFile.setFolderId(targetFolderId);
            cdFileMapper.updateById(cdFile);
            
            // 更新文件夹统计
            updateFolderStats(cdFile.getFolderId());
            updateFolderStats(targetFolderId);
            
            return true;
            
        } catch (Exception e) {
            log.error("移动文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("移动文件失败: " + e.getMessage());
        }
    }

    /**
     * 复制文件
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean copyFile(Long fileId, Long targetFolderId) {
        try {
            // 获取文件信息
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                throw new RuntimeException("文件不存在");
            }
            
            // 检查权限
            permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_READ, "无权限复制此文件");
            
            // 验证目标文件夹
            CdFolder targetFolder = validateFolder(targetFolderId);
            
            // 检查目标文件夹下是否有重名文件
            LambdaQueryWrapper<CdFile> wrapper = new LambdaQueryWrapper<CdFile>();
            wrapper.eq(CdFile::getFolderId, targetFolderId)
                   .eq(CdFile::getOriginalName, cdFile.getOriginalName());
            
            if (cdFileMapper.selectCount(wrapper) > 0) {
                throw new RuntimeException("目标文件夹下已存在同名文件");
            }
            
            // 创建文件副本
            CdFile newFile = new CdFile();
            BeanUtil.copyProperties(cdFile, newFile);
            newFile.setFileId(null);
            newFile.setFolderId(targetFolderId);
            newFile.setDownloadCount(0);
            newFile.setViewCount(0);
            newFile.setIsShared(false);
            newFile.setShareCode(null);
            newFile.setShareExpireTime(null);
            newFile.setParentFileId(cdFile.getFileId());
            newFile.setCreateTime(new Date());
            newFile.setUpdateTime(new Date());
            
            cdFileMapper.insert(newFile);
            
            // 更新文件夹统计
            updateFolderStats(targetFolderId);
            
            // 更新用户存储统计
            updateUserStorageStats(cdFile.getFileSize(), 1, 0);
            
            return true;
            
        } catch (Exception e) {
            log.error("复制文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("复制文件失败: " + e.getMessage());
        }
    }

    /**
     * 删除文件
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteFiles(List<Long> fileIds) {
        try {
            if (ObjectUtil.isEmpty(fileIds)) {
                return true;
            }
            
            // 检查权限并删除文件
            for (Long fileId : fileIds) {
                CdFile cdFile = cdFileMapper.selectById(fileId);
                if (cdFile == null) {
                    continue;
                }
                
                // 检查权限
                permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_DELETE, "无权限删除文件: " + cdFile.getOriginalName());
                
                // 删除文件记录（软删除）
                cdFileMapper.deleteById(fileId);
                
                // 更新文件夹统计
                updateFolderStats(cdFile.getFolderId());
                
                // 更新用户存储统计
                updateUserStorageStats(-cdFile.getFileSize(), -1, 0);
            }
            
            return true;
            
        } catch (Exception e) {
            log.error("删除文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("删除文件失败: " + e.getMessage());
        }
    }

    /**
     * 查询文件列表（包含子文件夹的文件）
     */
    @Override
    public R<List<CdFileVo>> queryFileList(Long folderId, String fileName, String fileType, CdFileVo pageQuery) {
        try {
            // 如果是根文件夹（folderId = 0），需要获取用户的根文件夹ID
            Long actualFolderId = folderId;
            if (folderId == 0L) {
                Long currentUserId = LoginHelper.getUserId();
                // 检查是否已创建根文件夹
                LambdaQueryWrapper<CdFolder> rootWrapper = new LambdaQueryWrapper<CdFolder>();
                rootWrapper.eq(CdFolder::getParentId, 0L)
                          .eq(CdFolder::getFolderName, "我的文件")
                          .eq(CdFolder::getFolderType, "personal")
                          .eq(CdFolder::getOwnerId, currentUserId)
                          .eq(CdFolder::getDelFlag, "0");
                
                List<CdFolder> rootFolders = cdFolderMapper.selectList(rootWrapper);
                if (rootFolders.isEmpty()) {
                    // 创建用户的根文件夹
                    cdInitializer.createUserRootFolder(currentUserId);
                    cdInitializer.initUserStorage(currentUserId);
                    rootFolders = cdFolderMapper.selectList(rootWrapper);
                }
                
                if (!rootFolders.isEmpty()) {
                    actualFolderId = rootFolders.get(0).getFolderId();
                }
            }
            
            // 验证文件夹（如果actualFolderId不是0）
            if (actualFolderId != 0L) {
                validateFolder(actualFolderId);
            }
            
            // 获取所有子文件夹ID（包括当前文件夹）
            List<Long> folderIds = getAllSubFolderIds(actualFolderId);
            log.info("查询文件列表 - folderId: {}, actualFolderId: {}, 包含的子文件夹数量: {}", folderId, actualFolderId, folderIds.size());
            
            // 获取当前用户ID
            Long currentUserId = LoginHelper.getUserId();
            
            // 构建查询条件
            LambdaQueryWrapper<CdFile> wrapper = new LambdaQueryWrapper<CdFile>();
            wrapper.in(CdFile::getFolderId, folderIds); // 使用 in 查询所有子文件夹
            
            if (StrUtil.isNotBlank(fileName)) {
                wrapper.like(CdFile::getOriginalName, fileName);
            }
            
            if (StrUtil.isNotBlank(fileType)) {
                wrapper.eq(CdFile::getFileType, fileType);
            }
            
            wrapper.orderByDesc(CdFile::getCreateTime);
            
            // 分页查询
            List<CdFile> list = cdFileMapper.selectList(wrapper);
            
            // 过滤有权限的文件（owner或有读取权限）
            List<CdFile> authorizedList = list.stream()
                    .filter(file -> {
                        // 所有者拥有所有权限
                        if (file.getCreateBy() != null && file.getCreateBy().equals(currentUserId)) {
                            return true;
                        }
                        // 检查是否有读取权限
                        return permissionUtils.checkFileReadPermission(file.getFileId());
                    })
                    .collect(Collectors.toList());
            
            // 转换为VO
            List<CdFileVo> voList = authorizedList.stream()
                    .map(this::buildFileVo)
                    .collect(Collectors.toList());
            
            return R.ok(voList);
            
        } catch (Exception e) {
            log.error("查询文件列表失败: {}", e.getMessage(), e);
            throw new RuntimeException("查询文件列表失败: " + e.getMessage());
        }
    }
    
    /**
     * 递归获取所有子文件夹ID
     */
    private List<Long> getAllSubFolderIds(Long folderId) {
        List<Long> result = new ArrayList<>();
        
        // 添加当前文件夹ID
        result.add(folderId);
        
        // 查询子文件夹
        LambdaQueryWrapper<CdFolder> wrapper = new LambdaQueryWrapper<CdFolder>();
        wrapper.eq(CdFolder::getParentId, folderId);
        wrapper.eq(CdFolder::getDelFlag, "0");
        
        List<CdFolder> subFolders = cdFolderMapper.selectList(wrapper);
        
        // 递归查询子文件夹的文件
        for (CdFolder subFolder : subFolders) {
            result.addAll(getAllSubFolderIds(subFolder.getFolderId()));
        }
        
        return result;
    }

    /**
     * 搜索文件
     */
    @Override
    public R<List<CdFileVo>> searchFiles(String keyword, String fileType, CdFileVo pageQuery) {
        try {
            Long userId = LoginHelper.getUserId();
            
            // 构建查询条件
            LambdaQueryWrapper<CdFile> wrapper = new LambdaQueryWrapper<CdFile>();
            wrapper.eq(CdFile::getCreateBy, userId.toString());
            
            if (StrUtil.isNotBlank(keyword)) {
                wrapper.like(CdFile::getOriginalName, keyword);
            }
            
            if (StrUtil.isNotBlank(fileType)) {
                wrapper.eq(CdFile::getFileType, fileType);
            }
            
            wrapper.orderByDesc(CdFile::getCreateTime);
            
            // 分页查询
            List<CdFile> list = cdFileMapper.selectList(wrapper);
            
            // 转换为VO
            List<CdFileVo> voList = list.stream()
                    .map(this::buildFileVo)
                    .collect(Collectors.toList());
            
            return R.ok(voList);
            
        } catch (Exception e) {
            log.error("搜索文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("搜索文件失败: " + e.getMessage());
        }
    }

    /**
     * 获取文件详情
     */
    @Override
    public CdFileVo getFileInfo(Long fileId) {
        try {
            CdFile cdFile = cdFileMapper.selectById(fileId);
            if (cdFile == null) {
                throw new RuntimeException("文件不存在");
            }
            
            // 检查权限
            permissionUtils.validateFilePermission(fileId, CdPermissionUtils.PERMISSION_READ, "无权限查看此文件");
            
            return buildFileVo(cdFile);
            
        } catch (Exception e) {
            log.error("获取文件详情失败: {}", e.getMessage(), e);
            throw new RuntimeException("获取文件详情失败: " + e.getMessage());
        }
    }

    /**
     * 检查文件权限
     */
    @Override
    /**
     * 检查文件权限（已废弃，使用CdPermissionUtils）
     */
    @Deprecated
    public Boolean checkFilePermission(Long fileId, String permissionType) {
        return permissionUtils.checkFilePermission(fileId, permissionType);
    }

    // ==================== 私有方法 ====================

    /**
     * 验证文件
     */
    private void validateFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new RuntimeException("文件不能为空");
        }
        
        // 检查文件大小（从配置文件读取）
        long maxSize = parseSizeToBytes(maxFileSize);
        if (file.getSize() > maxSize) {
            long maxSizeMB = maxSize / (1024 * 1024);
            throw new RuntimeException("文件大小不能超过" + maxSizeMB + "MB");
        }
        
        // 检查文件类型
        String originalName = file.getOriginalFilename();
        if (StrUtil.isBlank(originalName)) {
            throw new RuntimeException("文件名不能为空");
        }
    }

    /**
     * 验证文件夹
     */
    private CdFolder validateFolder(Long folderId) {
        if (folderId == null) {
            throw new RuntimeException("文件夹ID不能为空");
        }
        
        // 支持根文件夹（folderId = 0）
        if (folderId == 0L) {
            return null; // 根文件夹
        }
        
        CdFolder folder = cdFolderMapper.selectById(folderId);
        if (folder == null) {
            throw new RuntimeException("文件夹不存在");
        }
        
        return folder;
    }

    /**
     * 检查用户存储空间
     */
    private void checkUserStorageSpace(long fileSize) {
        Long userId = LoginHelper.getUserId();
        CdUserStorage userStorage = cdUserStorageMapper.selectStorageByUserId(userId);
        
        if (userStorage != null) {
            long totalSize = userStorage.getTotalSize() != null ? userStorage.getTotalSize() : 0;
            long usedSize = userStorage.getUsedSize() != null ? userStorage.getUsedSize() : 0;
            
            if (usedSize + fileSize > totalSize) {
                throw new RuntimeException("存储空间不足");
            }
        }
    }

    /**
     * 上传文件到存储
     */
    private String uploadFileToStorage(MultipartFile file, String fileName) throws IOException, com.nbcio.common.core.exception.file.InvalidExtensionException {
        String uploadType = com.nbcio.common.core.config.RuoYiConfig.getUploadtype();
        
        if ("local".equals(uploadType)) {
            // 本地存储 - 参考PM项目的实现
            String profile = com.nbcio.common.core.config.RuoYiConfig.getProfile();
            
            // 临时修复：如果profile路径末尾有'e'，去掉它
            if (profile.endsWith("/e")) {
                profile = profile.substring(0, profile.length() - 2);
            } else if (profile.endsWith("e")) {
                profile = profile.substring(0, profile.length() - 1);
            }
            
            // 使用修复后的profile重新计算uploadPath
            String uploadPath = profile + "/upload/cd";
            
            String filePath = com.nbcio.common.core.utils.file.FileUploadUtils.upload(uploadPath, file, com.nbcio.common.core.utils.file.MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
            
            return filePath;
        } else if ("minio".equals(uploadType) || "alioss".equals(uploadType)) {
            // OSS存储
            com.nbcio.common.oss.core.OssClient ossClient = com.nbcio.common.oss.factory.OssFactory.instance();
            UploadResult uploadResult = ossClient.uploadSuffix(
                    file.getInputStream(), 
                    "." + CdFileUtils.getFileExtension(fileName), 
                    file.getContentType()
            );
            return uploadResult.getUrl();
        } else {
            throw new RuntimeException("不支持的存储类型: " + uploadType);
        }
    }

    /**
     * 创建文件引用
     */
    private CdFileVo createFileReference(CdFile existingFile, Long folderId, String originalName) {
        CdFile newFile = new CdFile();
        BeanUtil.copyProperties(existingFile, newFile);
        newFile.setFileId(null);
        newFile.setFolderId(folderId);
        newFile.setOriginalName(originalName);
        newFile.setDownloadCount(0);
        newFile.setViewCount(0);
        newFile.setIsShared(false);
        newFile.setShareCode(null);
        newFile.setShareExpireTime(null);
        newFile.setCreateTime(new Date());
        newFile.setUpdateTime(new Date());
        
        cdFileMapper.insert(newFile);
        
        // 更新文件夹统计
        updateFolderStats(folderId);
        
        return buildFileVo(newFile);
    }

    /**
     * 更新文件夹统计
     */
    private void updateFolderStats(Long folderId) {
        // 如果 folderId 为 null 或 0（根文件夹），不更新统计
        if (folderId == null || folderId == 0L) {
            log.debug("跳过根文件夹统计更新，folderId: {}", folderId);
            return;
        }
        
        // 统计文件数量和大小
        LambdaQueryWrapper<CdFile> wrapper = new LambdaQueryWrapper<CdFile>();
        wrapper.eq(CdFile::getFolderId, folderId);
        
        List<CdFile> files = cdFileMapper.selectList(wrapper);
        int fileCount = files.size();
        long folderSize = files.stream().mapToLong(CdFile::getFileSize).sum();
        
        // 更新文件夹统计
        cdFolderMapper.updateFileCount(folderId, fileCount);
        cdFolderMapper.updateFolderSize(folderId, folderSize);
    }

    /**
     * 更新用户存储统计
     */
    private void updateUserStorageStats(long sizeChange, int fileCountChange, int folderCountChange) {
        Long userId = LoginHelper.getUserId();
        CdUserStorage userStorage = cdUserStorageMapper.selectStorageByUserId(userId);
        
        if (userStorage == null) {
            // 创建新的存储统计记录
            userStorage = new CdUserStorage();
            userStorage.setUserId(userId);
            userStorage.setTotalSize(1024L * 1024 * 1024); // 默认1GB
            userStorage.setUsedSize(0L);
            userStorage.setFileCount(0);
            userStorage.setFolderCount(0);
            userStorage.setStatus("0");
            cdUserStorageMapper.insert(userStorage);
        }
        
        // 更新统计信息
        long newUsedSize = userStorage.getUsedSize() + sizeChange;
        int newFileCount = userStorage.getFileCount() + fileCountChange;
        int newFolderCount = userStorage.getFolderCount() + folderCountChange;
        
        cdUserStorageMapper.updateUserStorage(userId, newUsedSize, newFileCount, newFolderCount);
    }

    /**
     * 构建文件VO
     */
    private CdFileVo buildFileVo(CdFile cdFile) {
        CdFileVo vo = new CdFileVo();
        
        // 手动复制属性
        vo.setFileId(cdFile.getFileId());
        vo.setFileName(cdFile.getFileName());
        vo.setOriginalName(cdFile.getOriginalName());
        vo.setFilePath(cdFile.getFilePath());
        vo.setFileSize(cdFile.getFileSize());
        vo.setFileType(cdFile.getFileType());
        vo.setFolderId(cdFile.getFolderId());
        vo.setIsShared(cdFile.getIsShared());
        vo.setAccessLevel(cdFile.getAccessLevel());
        vo.setStatus(cdFile.getStatus());
        vo.setCreateBy(cdFile.getCreateBy());
        vo.setCreateTime(cdFile.getCreateTime());
        vo.setUpdateBy(cdFile.getUpdateBy());
        vo.setUpdateTime(cdFile.getUpdateTime());
        vo.setDelFlag(cdFile.getDelFlag());
        vo.setCreateDept(cdFile.getCreateDept());
        
        // 设置格式化的大小
        vo.setFileSizeFormatted(CdFileUtils.formatFileSize(cdFile.getFileSize()));
        
        // 设置文件URL和预览URL
        // 文件URL使用原始路径
        vo.setFileUrl(cdFile.getFilePath());
        // 预览URL使用下载接口（直接返回文件流）
        if (CdFileUtils.canPreview(cdFile.getOriginalName())) {
            vo.setPreviewUrl("/cd/file/download/" + cdFile.getFileId());
        } else {
            vo.setPreviewUrl(cdFile.getFilePath());
        }
        
        // 设置是否可预览和下载
        vo.setCanPreview(CdFileUtils.canPreview(cdFile.getOriginalName()));
        vo.setCanDownload(true);
        
        // 设置文件夹路径（显示文件在文件夹树中的位置）
        String folderPath = getFolderPath(cdFile.getFolderId());
        vo.setFolderPath(folderPath);
        
        // 设置是否为当前用户拥有
        Long currentUserId = LoginHelper.getUserId();
        // 注意：cdFile.getCreateBy() 可能返回String，需要转换
        Long fileOwnerId = cdFile.getCreateBy() != null ? Long.valueOf(cdFile.getCreateBy().toString()) : null;
        vo.setIsOwned(fileOwnerId != null && fileOwnerId.equals(currentUserId));
        
        return vo;
    }
    
    /**
     * 获取文件夹路径（递归获取父文件夹名称，用/分隔）
     */
    private String getFolderPath(Long folderId) {
        if (folderId == null || folderId == 0L) {
            return "/";
        }
        
        List<String> folderNames = new ArrayList<>();
        Long currentFolderId = folderId;
        
        // 递归查找父文件夹
        while (currentFolderId != null && currentFolderId != 0L) {
            CdFolder folder = cdFolderMapper.selectById(currentFolderId);
            if (folder == null) {
                break;
            }
            folderNames.add(0, folder.getFolderName()); // 添加到列表开头
            currentFolderId = folder.getParentId();
        }
        
        // 拼接路径
        if (folderNames.isEmpty()) {
            return "/";
        }
        
        return "/" + String.join("/", folderNames);
    }

    /**
     * 将大小字符串（如"200MB"）转换为字节数
     */
    private long parseSizeToBytes(String sizeStr) {
        if (StrUtil.isBlank(sizeStr)) {
            return 200 * 1024 * 1024; // 默认200MB
        }
        
        try {
            // 转换为小写并去除空格
            String size = sizeStr.trim().toLowerCase();
            long multiplier = 1;
            
            if (size.endsWith("kb")) {
                multiplier = 1024;
                size = size.substring(0, size.length() - 2);
            } else if (size.endsWith("mb")) {
                multiplier = 1024 * 1024;
                size = size.substring(0, size.length() - 2);
            } else if (size.endsWith("gb")) {
                multiplier = 1024 * 1024 * 1024;
                size = size.substring(0, size.length() - 2);
            } else if (size.endsWith("b")) {
                multiplier = 1;
                size = size.substring(0, size.length() - 1);
            }
            
            long value = Long.parseLong(size.trim());
            return value * multiplier;
        } catch (Exception e) {
            log.warn("解析文件大小配置失败: {}, 使用默认值200MB", sizeStr, e);
            return 200 * 1024 * 1024; // 默认200MB
        }
    }
}
